/**
* <p>Title: XML Interactions</p>
* <p>Description: CS343 Project</p>
* @author Derek Meisner
* @author Conor Lauer
* @author Derek Thompson
* @email meis0056@umn.edu
* @email conor.lauer@my.uwrf.edu
* @email derek.thompson-1@my.uwrf.edu
* @date 11/14/2012
* @team Derek, Conor, Derek
*/

package golfScore;

import java.io.File;
import java.io.IOException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.xml.sax.SAXException;


public class XML_Interactions extends Thread{
	
	private String courseFilePath;
	private String userFilePath;
	private Document courseData;
	private Document userData;
	private Course saveCourse;
	private User saveUser;
	private int[] userScores;
	private boolean isDoneButton;
	
	/**
	   * constructor
	   * @require courseFilePath <> null, userFilePath <> null
	   * @ensure
	   */
	public XML_Interactions( String courseFilePath, String userFilePath ){
		this.courseFilePath = courseFilePath;
		this.userFilePath = userFilePath;
		this.courseData = getDocument( this.courseFilePath );
		this.userData = getDocument( this.userFilePath );
	}
	
	public XML_Interactions( String userFilePath, Course course, User user, int[] userScores, boolean isDoneButton){
		this.userFilePath = userFilePath;
		this.userData = getDocument( this.userFilePath );
		this.saveCourse = course;
		this.saveUser = user;
		this.userScores = userScores;
		this.isDoneButton = isDoneButton;
	}
	
	/**
	   * getDocument
	   * @require filePath <> null
	   * @ensure
	   */
	public Document getDocument( String filePath ){
		/*
         * Set up the file for reading
         */
        File inFile = new File( filePath );
        Document inDoc = null;
        
        /*
         * Open the file as an XML file and parse (read) the data within if possible
         */
        try {
            DocumentBuilder docbuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            inDoc = docbuilder.parse(inFile);
        } catch (ParserConfigurationException e) {
            System.err.println("Not able to create DocumentBuilder object!");
            e.printStackTrace();
        } catch (SAXException e) {
            System.err.println("Not able to read XML file: " + inFile );
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("Not able to open file: " + inFile );
            e.printStackTrace();
        }
		return inDoc;
	}
	
	/**
	 * 	getCourseNames()
	 * 
	 * @returns Object[] List of string course names parsed from the document
	 * @assert courseData.xml must contain root golf, and child course, 
	 * 			each child course element must have a name tag in it.
	 */
	public Object[] getCourseNames(){
        //Get a count of the courses
        XPath path = XPathFactory.newInstance().newXPath();
        final String inspectString_numberOfCourses = "count(/golf/course)";
        int numberOfCourses = 0;
        
        try {
             numberOfCourses = Integer.parseInt( path.evaluate(inspectString_numberOfCourses, courseData ));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }

        Object[] coursesAvailable = new Object[numberOfCourses];
        
        //Get all course names to put in out combo box
        for(int i = 0; i < numberOfCourses; i++){    
            try {
            	coursesAvailable[i] = path.evaluate("/golf/course[" + (i+1) + "]/name", courseData);
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }
		return coursesAvailable;
	}
	
	/**
	 * 	getCourse()
	 * 
	 * @returns Course
	 * @assert courseName <> null
	 */
	public Course getCourse(String courseName){
        //Get a count of the courses 
        XPath path = XPathFactory.newInstance().newXPath();
        final String inspectString_numberOfCourses = "count(/golf/course)";
        int numberOfCourses = 0;
        
        try {
        	numberOfCourses = Integer.parseInt( path.evaluate(inspectString_numberOfCourses, courseData ));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }

        String courseNameTemp;
        float rating = 0;
        int slope = 0;
        Course course = new Course(courseName);
        
        //Get all course names to put in out combo box
        for(int i = 0; i < numberOfCourses; i++){    
            try {
            	courseNameTemp = path.evaluate("/golf/course[" + (i+1) + "]/name", courseData);
            	if (courseNameTemp.equals(courseName)){
            		rating = Float.parseFloat(path.evaluate("/golf/course[" + (i+1) + "]/rating", courseData));
            		slope = Integer.parseInt(path.evaluate("/golf/course[" + (i+1) + "]/slope", courseData));
            		int numberOfHoles, dist, par, holeVal, handicap;
            		String holeHead;
            		numberOfHoles = Integer.parseInt(path.evaluate("count(/golf/course[" + (i+1) + "]/hole)", courseData));
            		for (int j = 0; j < numberOfHoles; j++){
            			dist = Integer.parseInt( path.evaluate("/golf/course[" + (i+1) + "]/hole[" + (j+1) + "]/distance", courseData));
            			par = Integer.parseInt( path.evaluate("/golf/course[" + (i+1) + "]/hole[" + (j+1) + "]/par", courseData));
            			holeHead = path.evaluate("/golf/course[" + (i+1) + "]/hole[" + (j+1) + "]/number", courseData);
            			holeVal = Integer.parseInt( path.evaluate("/golf/course[" + (i+1) + "]/hole[" + (j+1) + "]/number", courseData));
            			handicap = Integer.parseInt( path.evaluate("/golf/course[" + (i+1) + "]/hole[" + (j+1) + "]/handicap", courseData));
            			course.addHole(dist, par, holeHead, holeVal, handicap);
            		}
            		break;
            	}
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }

		course.setRating(rating);
		course.setSlope(slope);
		
		return course;
	}
	
	/**
	 * 	getUser()
	 * 
	 * @returns User
	 * @assert userName <> null
	 */
	public User getUser(String userName){
        //Get a count of the users
        XPath path = XPathFactory.newInstance().newXPath();
        final String inspectString_numberOfUsers = "count(/users/user)";
        int numberOfUsers = 0;
        
        try {
            numberOfUsers = Integer.parseInt( path.evaluate(inspectString_numberOfUsers, userData ));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }

        String userNameTemp;
        float average = 0;
        int instance = 1;
        
        //Get all course names to put in out combo box
        for(int i = 0; i < numberOfUsers; i++){    
            try {
            	userNameTemp = path.evaluate("/users/user[" + (i+1) + "]/name", userData);
            	if (userNameTemp.equals(userName)){
            		average = Float.parseFloat(path.evaluate("/users/user[" + (i+1) + "]/average", userData));
            		instance = Integer.parseInt(path.evaluate("/users/user[" + (i+1) + "]/instanceCounter", userData));
            	}
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }
		return new User(userName, average, instance);
	}
	
	/**
	 * 	getUsers()
	 * 
	 * @returns Object[] List of string User names parsed from the document
	 * @assert courseData.xml must contain root users, and child user, 
	 * 			each child user element must have a name tag in it.
	 */
	public Object[] getUsers(){
        //Get a count of the users
        XPath path = XPathFactory.newInstance().newXPath();
        final String inspectString_numberOfUsers = "count(/users/user)";
        int numberOfUsers = 0;
        
        try {
             numberOfUsers = Integer.parseInt( path.evaluate(inspectString_numberOfUsers, userData ));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }

        Object[] usersAvailable = new Object[numberOfUsers];
        
        //Get all course names to put in out combo box
        for(int i = 0; i < numberOfUsers; i++){    
            try {
            	usersAvailable[i] = path.evaluate("/users/user[" + (i+1) + "]/name", userData);
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }
		return usersAvailable;
	}
	
	/**
	   * getCourseFilePath
	   * @require
	   * @ensure
	   */
	public String getCourseFilePath() {
		return courseFilePath;
	}
	
	/**
	   * getuserFilePath
	   * @require 
	   * @ensure
	   */
	public String getUserFilePath() {
		return userFilePath;
	}
	
	/**
	   * run
	   * @require
	   * @ensure
	   */
	public void run(){
		System.out.println("Saving Scores for - " + saveUser.getName());
		
		this.saveLogic();
		this.setAverageLogic();
	}
	
	public void saveLogic(){
		//Get a count of the users
        XPath path = XPathFactory.newInstance().newXPath();
        final String inspectString_numberOfUsers = "count(/users/user)";
        int numberOfUsers = 0;
        
        try {
            numberOfUsers = Integer.parseInt( path.evaluate(inspectString_numberOfUsers, userData ));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }

        String userNameTemp;
        String courseNameTemp;
        String instanceIdTemp;
        boolean courseFound = false;
        boolean instanceFound = false;
        Node userNode = null;
        
        //Where all the magic happens...
        for(int i = 0; i < numberOfUsers; i++){
            try {
            	userNameTemp = path.evaluate("/users/user[" + (i+1) + "]/name", userData);
            	if (userNameTemp.equals(saveUser.getName())){
            		System.out.println("User " + saveUser.getName() + " found!");
            		int saveDataCheck = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData)", userData ));
            		userNode = (Node) (path.evaluate("/users/user[" + (i+1) + "]", userData, XPathConstants.NODE));
            		
            		//If done button was pressed need to increment instance counter
        			if (isDoneButton){
        				Element instanceCounterElement = (Element) (path.evaluate("/users/user[" + (i+1) + "]/instanceCounter", userData, XPathConstants.NODE));
        				int currentInstanceID = Integer.parseInt(instanceCounterElement.getTextContent());
        				currentInstanceID++;
        				instanceCounterElement.setTextContent(Integer.toString(currentInstanceID));
        			}
            		
            		//No saveData under user therefore need to add it
            		if (saveDataCheck == 0)
            		{
            			System.out.println("Need to create saveData in XML file");
         			
            			Element newSaveDataNode = userData.createElement("saveData");
            			userNode.appendChild(newSaveDataNode);
            		}
            		
            		//Need to re-get saveData to make sure it was added correctly
            		saveDataCheck = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData)", userData ));
            		if (saveDataCheck > 0){
            			Node saveDataNode = (Node) path.evaluate("/users/user[" + (i+1) + "]/saveData", userData, XPathConstants.NODE );
            			int numberOfCourses = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData/course)", userData ));
            			Node courseNode = null;

            			//If numberOfCourses is > 0 Loop through number of courses
        				if (numberOfCourses > 0)
        				{
        					System.out.println("Number of courses found: " + numberOfCourses);
        					
        					for (int j = 0; j < numberOfCourses; j++){
        						courseNode = (Node) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[" + (j+1) + "]", userData, XPathConstants.NODE );
        						NamedNodeMap attr = courseNode.getAttributes();
        						courseNameTemp = attr.getNamedItem("name").getNodeValue();
        						if (courseNameTemp.equals(saveCourse.getCourseName())){
        							System.out.println("Course found: " + courseNameTemp);
        							courseFound = true;
        							break;
        						}
        					}
        				}
            		
        				if (!courseFound){
        					System.out.println("Course not found, need to add it!");
        					
        					//If course is not found we need to add the course node to saveDataNode
    						Element newCourseNode = userData.createElement("course");
    						newCourseNode.setAttribute("name", saveCourse.getCourseName());
    						saveDataNode.appendChild(newCourseNode);
    						
    						//Need to get new number of courses found and then loop through courses to get courseNode
    						numberOfCourses = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData/course)", userData ));
            				System.out.println("New number of courses found after adding course: " + numberOfCourses);
            				
            				for (int j = 0; j < numberOfCourses; j++){
        						courseNode = (Node) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[" + (j+1) + "]", userData, XPathConstants.NODE );
        						NamedNodeMap attr = courseNode.getAttributes();
        						courseNameTemp = attr.getNamedItem("name").getNodeValue();
        						if (courseNameTemp.equals(saveCourse.getCourseName())){
        							
        							System.out.println("Course found: " + courseNameTemp);
        							break;
        						}
            				}
        				}
        				
        				//Once we have our user, saveData, and course we can then look for our instance
        				int numberOfInstances = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData/course[@name='" + saveCourse.getCourseName() + "']/instance)", userData ));
        				Node instanceNode = null;
        				System.out.println("Number of instances: " + numberOfInstances);
        				
        				if (numberOfInstances > 0){
        					for (int j = 0; j < numberOfInstances; j++){
        						instanceNode = (Node) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[@name='" + saveCourse.getCourseName() + "']/instance[" + (j+1) + "]", userData, XPathConstants.NODE );
        						NamedNodeMap attr = instanceNode.getAttributes();
        						instanceIdTemp = attr.getNamedItem("id").getNodeValue();
        						if (instanceIdTemp.equals(Integer.toString(saveUser.getUserInstance()))){
        							System.out.println("Instance found: " + instanceIdTemp);
        							instanceFound = true;
        							break;
        						}
        					}
        				}

        				if (!instanceFound){
        					System.out.println("Instance is not found, need to add it!");
        					
        					//If instance is not found need to create it
    						Element newInstanceNode = userData.createElement("instance");
    						newInstanceNode.setAttribute("id", Integer.toString(saveUser.getUserInstance()));
    						courseNode.appendChild(newInstanceNode);
    						System.out.println(newInstanceNode.getAttribute("id"));
    						
    						numberOfInstances = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData/course[@name='" + saveCourse.getCourseName() + "']/instance)", userData ));
    						System.out.println("Number of instances after adding: " + numberOfInstances);
    						
    						for (int j = 0; j < numberOfInstances; j++){
        						instanceNode = (Node) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[@name='" + saveCourse.getCourseName() + "']/instance[" + (j+1) + "]", userData, XPathConstants.NODE );
        						NamedNodeMap attr = instanceNode.getAttributes();
        						instanceIdTemp = attr.getNamedItem("id").getNodeValue();
        						if (instanceIdTemp.equals(Integer.toString(saveUser.getUserInstance()))){
        							System.out.println("Instance found: " + instanceIdTemp);
        							instanceFound = true;
        							break;
        						}
        					}
        				}
        				
        				//Create a string of scores to add to element
        				String strScores = "";
        				for (int j = 0; j < userScores.length; j++)
        				{
        					if (j == 0)
        						strScores = Integer.toString(userScores[j]);
        					else if (j == userScores.length)
        						strScores = strScores + userScores[j];
        					else
        						strScores = strScores + ", " + userScores[j];
        				}
        				
        				Element scoreElement = (Element) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[@name='" + saveCourse.getCourseName() + "']/instance[@id='" + Integer.toString(saveUser.getUserInstance()) + "']/scores", userData, XPathConstants.NODE );
        				System.out.println("Score element before adding: " + scoreElement);
        				
        				if (scoreElement == null){
        					System.out.println("Score element not found, need to add it!");
        					
        					//If score is not found need to create it
    						Element newScoresNode = userData.createElement("scores");
    						instanceNode.appendChild(newScoresNode);
    						System.out.println(instanceNode);
    						System.out.println(newScoresNode);
    						
    						//Once element has been added, need to get it!
    						scoreElement = (Element) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[@name='" + saveCourse.getCourseName() + "']/instance[@id='" + Integer.toString(saveUser.getUserInstance()) + "']/scores", userData, XPathConstants.NODE );
        				}
        				
        				this.saveFile(3);
        				//Once all data is in XML file we can then add scores!
        				System.out.println(scoreElement);
        				System.out.println(saveUser.getUserInstance());
        				scoreElement.setTextContent(strScores);
				
            		}else{
            			System.out.println("Somehow saveData did not get created!");
            		}
            	}
            		
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }        
        //Save file with method below
        this.saveFile(3);		
	}
	
	public void setAverageLogic(){
		//Get a count of the users
        XPath path = XPathFactory.newInstance().newXPath();
        final String inspectString_numberOfUsers = "count(/users/user)";
        int numberOfUsers = 0;
        
        try {
            numberOfUsers = Integer.parseInt( path.evaluate(inspectString_numberOfUsers, userData ));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }

        String userNameTemp;
        
        //Where all the magic happens...
        for(int i = 0; i < numberOfUsers; i++){
            try {
            	float totalHoles = 0;
            	float totalScores = 0;
            	userNameTemp = path.evaluate("/users/user[" + (i+1) + "]/name", userData);
            	if (userNameTemp.equals(saveUser.getName())){
            		System.out.println("User " + saveUser.getName() + " found!");

            		int numberOfCourses = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData/course)", userData ));
        			for (int j = 0; j < numberOfCourses; j++){
        				int numberOfInstances = Integer.parseInt( path.evaluate("count(/users/user[" + (i+1) + "]/saveData/course[" + (j+1) + "]/instance)", userData ));
        				for (int k = 0; k < numberOfInstances; k++){
        					Element scoreElement = (Element) path.evaluate("/users/user[" + (i+1) + "]/saveData/course[" + (j+1) + "]/instance[" + (k+1) + "]/scores", userData, XPathConstants.NODE );
        					String strText = scoreElement.getTextContent();
        					String delims = "[,]";
        					String[] tokens = strText.split(delims);
        					for(String token : tokens){
        						if (Integer.parseInt(token.trim()) != 0){
            						totalHoles++;
            						totalScores = totalScores + Integer.parseInt(token.trim());
        						}
        					}
    					}
        			}
        			if (totalHoles != 0 && totalScores != 0)
        				saveUser.calculateAverage(totalHoles, totalScores);
                	
                	Element average = (Element) (path.evaluate("/users/user[" + (i+1) + "]/average", userData, XPathConstants.NODE));
    				average.setTextContent(Float.toString(saveUser.getAverage()));
    				break;
            	}
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }
        this.saveFile(3);
	}
	
	private void saveFile(int indentation){
		
        DOMSource source = new DOMSource(userData);

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = null;
		try {
			transformer = transformerFactory.newTransformer();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		}
        StreamResult result = new StreamResult(userFilePath);
        try {
        	transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        	transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(indentation));
			transformer.transform(source, result);
			
		} catch (TransformerException e) {
			e.printStackTrace();
		}
	}
}